import os
from pyglet.gl import *

from _global import enum, flags, degToRad
from vector import Vector3
from drawable import Drawable
from textures import TextureList
from camera import Camera
from load import LevelLoader, ModelLoader
from window import Window
from input import Gamepad


class Game(Drawable):
	"""Engine main class, one instance per game"""
	ModelType = enum('NULL', 'STL_BIN', 'STL_TEXT')
	LevelType = enum('NULL', 'JSON')
	
	def __init__(self):
		self._globals = {}			#dict for game global data
		self._light = False
		self.debugMode = False
		self.cameras = [Camera()]
		self.scenes = []
		self.scene = None
		self.models = ModelList()
		self.keys = []
		self.gamepad = Gamepad()
		self.window = None
	
	def __getitem__(self, key):
		return self._globals[key]
	def __setitem__(self, key, v):
		self._globals[key] = v
		
	def load(self):
		if len(self.scenes):
			self.scene = self.scenes[-1]
			self.scene.load(self)
	
	def draw(self):
		if len(self.scenes):
			cams = len(self.cameras)
			if cams == 2:
				width, height = self.window.width, self.window.height
				halfHeight = height / 2
				#for i in range(cams):
				
				#Upper cam
				glViewport(0, halfHeight, width, halfHeight)
				glMatrixMode(GL_PROJECTION)
				glLoadIdentity()
				gluPerspective(45.0, float(width)/float(halfHeight), 0.1, 1000.0)
				glMatrixMode(GL_MODELVIEW)
				
				glPushAttrib(GL_ENABLE_BIT|GL_SCISSOR_BIT)
				glEnable(GL_SCISSOR_TEST)
				glScissor(0, halfHeight, width, halfHeight)
				self.cameras[0].look()
				self.scenes[-1].draw()
				glPopAttrib()
				
				#Lower cam
				glViewport(0, 0, width, halfHeight)
				glMatrixMode(gl.GL_PROJECTION)
				glLoadIdentity()
				gluPerspective(45.0, float(width)/float(halfHeight), 0.1, 1000.0)
				glMatrixMode(gl.GL_MODELVIEW)
				glLoadIdentity()
				glPushAttrib(GL_ENABLE_BIT|GL_SCISSOR_BIT)
				glEnable(GL_SCISSOR_TEST)
				glScissor(0, 0, width, halfHeight)
				self.cameras[1].look()
				self.scenes[-1].draw()
				glPopAttrib()
			else:
				self.cameras[0].look()
				self.scenes[-1].draw()
	
	def step(self):
		if len(self.scenes):
			self.scenes[-1].step()
	
	def run(self):
		self.load()
		pyglet.app.run()
	
	def createWindow(self, caption, width, height, hz=60.):
		self.window = Window(self, caption, width, height, hz)
	
	def loadModel(self, file, type, id=0):
		fullPath = os.path.abspath('models') + os.sep + file
		model = ModelLoader()
		
		if type == Game.ModelType.STL_BIN:
			model.loadStlBinary(fullPath)
		elif type == Game.ModelType.STL_TEXT:
			model.loadStlText(fullPath)
		
		return model.loadList(id)
	
	def loadLevel(self, file, type=-1):
		if type == -1 or type == Game.LevelType.JSON:
			jsonLoad = LevelLoader()
			return jsonLoad.loadLevelJSON(os.path.abspath('lvl') + os.sep + file)
		return ''
	
	def setColor(self, r, g, b):
		glColor3f(r, g, b)
	
	@property
	def light(self):
		return self._light
	@light.setter
	def light(self, b):
		if b:
			glEnable(GL_LIGHTING)
		else:
			glDisable(GL_LIGHTING)
		self._light = b
		
#Scene - abstract level/menu base class
class Scene(Drawable):
	def __init__(self):
		super(Scene, self).__init__()
		self.drawables = []
		self.textures = TextureList()
		#self.fps_display = pyglet.clock.ClockDisplay()
	
	def load(self, game):
		self.game = game
		if len(self.drawables):
			for d in self.drawables:
				d.load(self.game)
	
	def draw(self):
		if len(self.drawables):
			for d in self.drawables:
				d.draw()
		#self.fps_display.draw()
	
	def step(self):
		if len(self.drawables):
			#First, reset physics info
			#TODO: make a flag for this automatically (if an added draw has phys)
			#	to avoid the extra loop for scenes that don't need it
			for d in self.drawables:
				if d.phys: d.phys.clear()
			#Step all the child drawables
			for d in self.drawables:
				d.step()
		self.game.window.set_caption("fps=" + str(pyglet.clock.get_fps()))
	
	def checkCollisions(self, d, flags=Drawable.CollisionFlags.ALL, customFlags=0):
		"""Get a list of tuples (Drawable, Drawable.CollisionFlag.*)"""
		hit = []
		diff = d.moveTo - d.origin
		for d2 in self.drawables:
			#2 checks: d2 is involved in CD, d2 isnt d
			if d2.coll and not d == d2:
				#if it has custom flags and they dont match
				if d2.coll.customFlags and not d2.coll.customFlags & customFlags:
					continue
				
				#match = Vector3(0, 0, 0)
				#check individual AABB sides if flagged
				if flags & Drawable.CollisionFlags.X_POS: #Check +x
					if d.coll.box.right < d2.coll.box.left:
						if d.coll.box.right + diff.x >= d2.coll.box.left:
							#match.x = 1
							if self._withinBounds(d.coll.box, diff.y, d2.coll.box, Drawable.Bounds.Y) and self._withinBounds(d.coll.box, diff.z, d2.coll.box, Drawable.Bounds.Z):
								if not d.coll.bound: #no further bound checks
									overlap = d2.coll.box.left - d.coll.box.right - diff.x
									d.moveTo.x = d.origin.x + overlap
									hit.append((d2, Drawable.CollisionFlags.X_POS))
								else:
									pass #add call to other bounding volume check
				if flags & Drawable.CollisionFlags.X_NEG: #Check -x
					if d.coll.box.left > d2.coll.box.right:
						if d.coll.box.left + diff.x <= d2.coll.box.right:
							#match.x = 1
							if self._withinBounds(d.coll.box, diff.y, d2.coll.box, Drawable.Bounds.Y) and self._withinBounds(d.coll.box, diff.z, d2.coll.box, Drawable.Bounds.Z):
								if not d.coll.bound: #no further bound checks
									overlap = d.coll.box.left - d2.coll.box.right - diff.x
									d.moveTo.x = d.origin.x + overlap
									hit.append((d2, Drawable.CollisionFlags.X_NEG))
								else:
									pass #add call to other bounding volume check
				if flags & Drawable.CollisionFlags.Y_POS: #Check +y
					if d.coll.box.top < d2.coll.box.bottom:
						if d.coll.box.top + diff.y >= d2.coll.box.bottom:
							#match.y = 1
							if self._withinBounds(d.coll.box, diff.x, d2.coll.box, Drawable.Bounds.X) and self._withinBounds(d.coll.box, diff.z, d2.coll.box, Drawable.Bounds.Z):
								if not d.coll.bound: #no further bound checks
									overlap = d2.coll.box.bottom - d.coll.box.top - diff.y
									d.moveTo.y = d.origin.y + overlap
									hit.append((d2, Drawable.CollisionFlags.Y_POS))
								else:
									pass #add call to other bounding volume check
				if flags & Drawable.CollisionFlags.Y_NEG: #Check -y
					if d.coll.box.bottom > d2.coll.box.top:
						if d.coll.box.bottom + diff.y <= d2.coll.box.top:
							#match.y = 1
							if self._withinBounds(d.coll.box, diff.x, d2.coll.box, Drawable.Bounds.X) and self._withinBounds(d.coll.box, diff.z, d2.coll.box, Drawable.Bounds.Z):
								if not d.coll.bound: #no further bound checks
									overlap = d2.coll.box.top - d.coll.box.bottom - diff.y
									d.moveTo.y = d.origin.y + overlap #TODO: this seems like specific bounce code
									hit.append((d2, Drawable.CollisionFlags.Y_NEG))
								else:
									pass #add call to other bounding volume check
				if flags & Drawable.CollisionFlags.Z_POS: #Check +z
					if d.coll.box.front < d2.coll.box.back:
						if d.coll.box.front + diff.z >= d2.coll.box.back:
							#match.z = 1
							if self._withinBounds(d.coll.box, diff.x, d2.coll.box, Drawable.Bounds.X) and self._withinBounds(d.coll.box, diff.y, d2.coll.box, Drawable.Bounds.Y):
								if not d.coll.bound: #no further bound checks
									d.moveTo.z = d.origin.z
									hit.append((d2, Drawable.CollisionFlags.Z_POS))
								else:
									pass #add call to other bounding volume check
				if flags & Drawable.CollisionFlags.Z_NEG: #Check -z
					if d.coll.box.back > d2.coll.box.front:
						if d.coll.box.back + diff.z <= d2.coll.box.front:
							#match.z = 1
							if self._withinBounds(d.coll.box, diff.x, d2.coll.box, Drawable.Bounds.X) and self._withinBounds(d.coll.box, diff.y, d2.coll.box, Drawable.Bounds.Y):
								if not d.coll.bound: #no further bound checks
									d.moveTo.z = d.origin.z
									hit.append((d2, Drawable.CollisionFlags.Z_NEG))
								else:
									pass #add call to other bounding volume check
		return hit
	
	def _withinBounds(self, d1, v, d2, bounds):
		"""Real simple check for AABB match on one axis per call
			Args:
				b1:		First drawable's AABB moving into the other
				v:		Distance d1 is moving along single axis
				b2:		Second drawable's AABB being hit
				bounds:	X, Y or Z constant (Drawable.Bounds.*)
		"""
		if bounds == Drawable.Bounds.X:
			right = d1.right + v
			left = d1.left + v
			if right >= d2.left and right <= d2.right or left <= d2.right and left >= d2.left:
				return True
		elif bounds == Drawable.Bounds.Y:
			top = d1.top + v
			bottom = d1.bottom + v
			if top >= d2.bottom and top <= d2.top or bottom <= d2.top and bottom >= d2.bottom:
				return True
		elif bounds == Drawable.Bounds.Z:
			front = d1.front + v
			back = d1.back + v
			if front >= d2.back and front <= d2.front or back <= d2.front and back >= d2.back:
				return True
		return False

class ModelList(object):
	def __init__(self):
		self._model = {}
	
	def __getitem__(self, i):
		if i not in self._model:
			self._model[i] = glGenLists(1)
		return self._model[i]
	def __setitem__(self, i, m):
		self._model[i] = m
	def __contains__(self, i):
		return i in self._model
	
	def new(self):
		m = glGenLists(1)
		self._model.append(m)
		return m
	
	def all(self):
		for m in self._model:
			yield m
	
	def clear(self):
		for m in self._model:
			glDeleteLists(m, 1)
		m = {}
	
	def delete(self, m):
		self._model.remove(m)
		glDeleteLists(m, 1)